﻿using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Diagnostics;
using UtfprLibrary.Exceptions;

namespace UtfprLibrary.Libs {
    /// <summary>
    /// UnmanagedLibraryManager solves loading and unloading unmanaged libraries from UtfprLibrary.
    /// http://sabsthoughts.wordpress.com/2008/02/27/using-c-dll-in-aspnet-c/
    /// </summary>
    public static class UnmanagedLibraryManager {
        #region Private

        /// <summary>
        /// Get a temporary directory in which we can store the unmanaged DLL, with
        /// this assembly's version number in the path in order to avoid version
        /// conflicts in case two applications are running at once with different versions
        /// </summary>
        /// <param name="dllName"></param>
        private static string getTempDllPath(string dllName) {
            try {
                string dirName = Path.Combine(Path.GetTempPath(),
                    Assembly.GetExecutingAssembly().GetName().Name + "." +
                    Assembly.GetExecutingAssembly().GetName().Version.ToString());
                if (!Directory.Exists(dirName)) {
                    Directory.CreateDirectory(dirName);
                }
                return Path.Combine(dirName, dllName);
            } catch (Exception ex) {
                throw new UtfprLibsException("Could not get the temporary dll path.", ex);
            }
        }

        /// <summary>
        /// Get the embedded resource stream that holds the Internal DLL in this assembly, and copy
        /// it to a temporary file.
        /// </summary>
        /// <param name="dllPath"></param>
        private static void copyAssemblyToTempFile(string dllName, string dllPath) {
            try {
                using (Stream stm = Assembly.GetExecutingAssembly()
                    .GetManifestResourceStream("UtfprLibrary.Libs." + dllName)) {
                    // Copy the assembly to the temporary file
                    try {
                        using (Stream outFile = File.Create(dllPath)) {
                            const int sz = 4096;
                            byte[] buf = new byte[sz];
                            while (true) {
                                int nRead = stm.Read(buf, 0, sz);
                                if (nRead < 1)
                                    break;
                                outFile.Write(buf, 0, nRead);
                            }
                        }
                    } catch {
                        // This may happen if another process has already created and loaded the file.
                        // Since the directory includes the version number of this assembly we can
                        // assume that it's the same bits, so we just ignore the excecption here and
                        // load the DLL.
                    }
                }
            } catch (Exception ex) {
                throw new UtfprLibsException("Could not copy assembly to the temporary file.", ex);
            }
        }

        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);

        [DllImport("kernel32.dll")]
        private static extern IntPtr FreeLibrary(IntPtr library);

        #endregion

        public static IntPtr LoadLib(string libraryName) {
            try {
                var libraryPath = getTempDllPath(libraryName);
                copyAssemblyToTempFile(libraryName, libraryPath);

                // We must explicitly load the DLL here because the temporary directory 
                // is not in the PATH.
                // Once it is loaded, the DllImport directives that use the DLL will use
                // the one that is already loaded into the process.

                return LoadLibrary(libraryPath);
            } catch (Exception ex) {
                throw new UtfprLibsException(Properties.Resources.Libs_UnmanagedLibraryManager_ExMsg_LoadLib, ex);
            }
        }

        public static void FreeLib(IntPtr library) {
            if (library != IntPtr.Zero) {
                try {
                    FreeLibrary(library);
                } catch (Exception ex) {
                    throw new UtfprLibsException(Properties.Resources.Libs_UnmanagedLibraryManager_ExMsg_FreeLib, ex);
                }
            }
        }
    }
}